Skip to content

fix(control-plane): map generated ApiException to typed OtariError#21

Merged
njbrake merged 3 commits into
mainfrom
fix/control-plane-error-mapping
Jul 21, 2026
Merged

fix(control-plane): map generated ApiException to typed OtariError#21
njbrake merged 3 commits into
mainfrom
fix/control-plane-error-mapping

Conversation

@njbrake

@njbrake njbrake commented Jun 25, 2026

Copy link
Copy Markdown
Member

Note: this PR was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.

Description

Control-plane resources (keys, users, budgets, pricing, usage) called the generated client directly, so any HTTP error surfaced as the raw otari._client.exceptions.ApiException rather than a typed otari.errors.OtariError. The inference path already maps generated exceptions in client.py, so the two surfaces had an inconsistent error contract: a bad master key on keys.list() raised an ApiException (and a full CLI traceback), while the inference path raised a clean AuthenticationError.

This makes the control plane match the inference path:

  • src/otari/_base.py: extract the existing mapping into module-level map_api_exception and extract_detail helpers (behavior-identical move). _BaseOtariClient._map_api_exception / _extract_detail now delegate to them, so the inference and streaming paths are unchanged.
  • src/otari/control_plane.py: add a small _translate decorator that catches ApiException and re-raises map_api_exception(exc), applied to all 22 ergonomic aliases. The raw generated surface is intentionally left unwrapped as the escape hatch.
  • tests/unit/test_control_plane_aliases.py: parametrized test asserting each resource alias surfaces a typed AuthenticationError (not a raw ApiException) on a 401, with status_code and detail preserved.

No public API change: the ergonomic aliases keep their signatures and return types; only the exception type on failure changes (raw ApiException becomes typed OtariError).

PR Type

  • New Feature
  • Bug Fix
  • Refactor
  • Documentation
  • Infrastructure / CI

Relevant issues

Fixes #20.

Checklist

  • I understand the code I am submitting.
  • I have added or updated tests that cover my change (tests/unit/test_control_plane_aliases.py).
  • I ran the Definition of Done checks locally (uv run ruff check ., uv run mypy src/, uv run pytest tests/unit).
  • Documentation was updated where necessary.
  • If the API contract changed, I regenerated the OpenAPI spec (n/a: no generated-core or spec change).

DoD results: ruff clean, mypy --strict clean (8 files), pytest tests/unit 127 passed.

AI Usage

  • No AI was used.
  • AI was used for drafting/refactoring.
  • This is fully AI-generated.

AI Model/Tool used: Claude Code (Claude Opus 4.8)

Any additional AI details you'd like to share: Generated via back-and-forth with @njbrake; the diagnosis and decisions are his.

  • I am an AI Agent filling out this form (check box if true)

Control-plane resources called the generated client directly, so HTTP errors
surfaced as the raw ApiException instead of a typed OtariError (the inference
path already maps these in client.py). Extract the mapping into module-level
map_api_exception/extract_detail helpers and route every control-plane
ergonomic alias through a _translate decorator; the raw escape hatch is left
unwrapped. Adds a parametrized unit test across all five resources.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@njbrake

njbrake commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

Note: this comment was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.

Cross-linking this to the cross-SDK side of the error-mapping work. The conformance assertion from mozilla-ai/otari#226 (a control-plane call must surface the same typed error the inference path raises, not the raw generated exception) now has a gateway-side anchor and a shared contract doc:

  • Gateway PR: test(integration): pin cross-SDK control-plane error contract otari#339
  • Shared contract: tests/integration/README.md in the gateway repo states the assertion each SDK suite implements, and test_control_plane_error_contract.py pins the server half: an invalid credential returns one uniform 401 with a JSON {"detail": ...} body on both the inference path and every control-plane router, so both are mappable to a single typed authentication error.

The test in this PR already implements the SDK side of that assertion across all five control-plane resources (keys, users, budgets, pricing, usage), mapping the generated ApiException to a typed AuthenticationError. That is what #226 asks for. Keeping the wording aligned with the gateway README should help the two ends of the wire stay in sync as either changes.

Keep the PR a focused bug fix: remove the uv.lock this branch added
(committing a lockfile for the first time is a project-wide decision
unrelated to the error-mapping fix, and CI does not consume it), and
collapse a stray extra blank line between the two new module functions
in _base.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@njbrake
njbrake force-pushed the fix/control-plane-error-mapping branch from 4bda867 to 5b88cd9 Compare July 21, 2026 16:24
…ifest

The gateway spec grew ~18 dashboard/admin management endpoints (aliases,
provider-credentials, providers, settings, model metadata/discovery,
usage/count, key rotate, budget reset-logs) that no SDK shell wraps yet.
The endpoint-coverage drift gate fetches the live gateway spec, so these
turned the gate red on every SDK independently of the error-mapping fix.
Defer them under [excluded] with a "not yet wrapped" reason to restore a
green gate; promote to [covered] when a shell surfaces them. Kept
identical across all four SDKs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@njbrake
njbrake marked this pull request as ready for review July 21, 2026 16:33
@njbrake
njbrake merged commit 47dd032 into main Jul 21, 2026
3 checks passed
@njbrake
njbrake deleted the fix/control-plane-error-mapping branch July 21, 2026 16:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Control-plane methods leak the raw generated ApiException instead of a typed OtariError

1 participant